/* RTC */
#include "rtc.h"
/*
* This file provides wrapper for DS1302. For detailed documentation,
* find datasheet of DS1302.
*/
#define RTC_PIN_CE      BIT5
#define RTC_PIN_IO      BIT4
#define RTC_PIN_SCLK    BIT3

#define RTC_REG_WRITE_SECOND    0x80
#define RTC_REG_READ_SECOND     0x81
#define RTC_REG_WRITE_MINUTE    0x82
#define RTC_REG_READ_MINUTE     0x83
#define RTC_REG_WRITE_HOUR      0x84
#define RTC_REG_READ_HOUR       0x85
#define RTC_REG_WRITE_DATE      0x86
#define RTC_REG_READ_DATE       0x87
#define RTC_REG_WRITE_MONTH     0x88
#define RTC_REG_READ_MONTH      0x89
#define RTC_REG_WRITE_WEEKDAY   0x8A
#define RTC_REG_READ_WEEKDAY    0x8B
#define RTC_REG_WRITE_YEAR      0x8C
#define RTC_REG_READ_YEAR       0x8D
#define RTC_REG_WRITE_CTRL      0x8E
#define RTC_REG_READ_CTRL       0x8F

void rtc_clk_pause()
{
  _DELAY();
  _DELAY();
}

#define rtc_ce_on() do {(P2OUT |= RTC_PIN_CE); rtc_clk_pause(); } while (0)
#define rtc_ce_off() do { (P2OUT &= ~RTC_PIN_CE); rtc_clk_pause(); } while (0)

#define rtc_clk_on() do { P2OUT |= RTC_PIN_SCLK; rtc_clk_pause(); } while (0)
#define rtc_clk_off() do { P2OUT &= ~RTC_PIN_SCLK; rtc_clk_pause(); } while (0)

static void rtc_io_out(uint8 value)
{
  //P2DIR |= RTC_PIN_IO;
  if (value)
    P2OUT |= RTC_PIN_IO;
  else
    P2OUT &= ~RTC_PIN_IO;
}

static uint8 rtc_io_in()
{
  //P2DIR &= ~RTC_PIN_IO;
  if (P2IN & RTC_PIN_IO)
    return 1;
  return 0;
}

void rtc_write_byte(uint8 value)
{
  // The data is sent to DS1302 at clock rising edge.
  // When this function enters, the clock bit is low.
  // When this function exits, the clock bit is low.
  uint8 i = 0;
  rtc_clk_off();
  P2DIR |= RTC_PIN_IO;
  for (i = 0; i < 8; i++)
  {
    rtc_io_out(value & BIT(i));
    rtc_clk_on();
    rtc_clk_off();
  }
  rtc_clk_off();
}

uint8 rtc_read_byte(void)
{
  // the data is read from DS1302 at clock falling edge.
  // When this function enters, the clock is low.
  // When this function exits, the clock is low.
  uint8 i = 0;
  uint8 result = 0;
  //rtc_clk_off();
  //rtc_clk_on();
  P2DIR &= ~RTC_PIN_IO;
  for (i = 0; i < 8; i++)
  {
    rtc_clk_off();
    result |= (rtc_io_in()? BIT(i): 0);
    rtc_clk_on();
  }
  rtc_clk_off();
  return result;
}

void rtc_write_register(uint8 addr, uint8 value)
{
  // when this function enters, ce is low
  // when this function exits, ce is low
  rtc_ce_off();
  rtc_ce_on();
  rtc_write_byte(addr);
  rtc_write_byte(value);
  rtc_ce_off();
}

uint8 rtc_read_register(uint8 addr)
{
  // when this function enters, ce is low
  // when this function exits, ce is low
  uint8 result = 0;
  rtc_ce_off();
  rtc_ce_on();
  rtc_write_byte(addr);
  result = rtc_read_byte();
  rtc_ce_off();
  return result;
}


void rtc_init(void)
{
  // enable P2.3, P2.4 and P2.5 for out
  P2DIR |= (RTC_PIN_SCLK | RTC_PIN_IO | RTC_PIN_CE);
}

void rtc_set_time(const struct DATE_TIME_T* time)
{
  rtc_init();
  // disable write protect
  rtc_write_register(RTC_REG_WRITE_CTRL, 0);
#define STORE(field, value) rtc_write_register(RTC_REG_WRITE_##field, bcd_encode(value))
  STORE(SECOND, time->second);
  STORE(MINUTE, time->minute);
  STORE(HOUR, time->hour);
  STORE(DATE, time->date);
  STORE(WEEKDAY, time->weekday);
  STORE(MONTH, time->month);
  STORE(YEAR, time->year-2000);

  // enable write protect
  rtc_write_register(RTC_REG_WRITE_CTRL, 0x80);
}

void rtc_get_time(struct DATE_TIME_T* time)
{
  rtc_init();

#define FETCH(field) bcd_decode(rtc_read_register(RTC_REG_READ_##field))
  //rtc_write_register(RTC_REG_WRITE_SECOND, 0);
  time->second = FETCH(SECOND);
  time->minute = FETCH(MINUTE);
  time->hour = FETCH(HOUR);
  time->date = FETCH(DATE);
  time->weekday = FETCH(WEEKDAY);
  time->month = FETCH(MONTH);
  time->year = 2000 + FETCH(YEAR);
}

static void time_next_day(uint16* year, uint8* month, uint8* date)
{
  if ((*date == 28) &&
      (*month == 2) &&
        ((*year % 4 != 0) || ((*year %100 == 0) && (*year % 400 != 0) )))
  {
    ++*month;
    *date = 1;
  }
  else if ((*date == 29) && (*month == 2))
  {
    ++*month;
    *date = 1;
  }
  else if ((*date == 30) &&
           (*month == 4 ||
            *month == 6 ||
              *month == 9 ||
                *month == 11))
  {
    ++*month;
    *date = 1;
  }
  else if (*date == 31)
  {
    *date = 1;
    if (*month == 12)
    {
      *month = 1;
      ++*year;
    }
    else
    {
      ++*month;
    }
  }
  else
  {
    ++*date;
  }
}

bool time_is_valid(const struct DATE_TIME_T* time)
{
  if ((time->year > 2016)   && (time->year < 2050)  &&
      (time->month > 0)     && (time->month < 13)   &&
      (time->date > 0)      &&  (time->date < 32)   &&      
      (time->minute < 60)   && (time->second < 60))
    return true;
  return false;
}

int16 time_compare(const struct DATE_TIME_T* a, const struct DATE_TIME_T* b)
{
  uint32 abs_second_a = (((uint32)a->year * 12 + a->month) * 31 + a->date) * 86400 +
      (uint32)a->hour *3600 + a->minute * 60 + a->second;
  uint32 abs_second_b = (((uint32)b->year * 12 + b->month) * 31 + b->date) * 86400 +
      (uint32)b->hour *3600 + b->minute * 60 + b->second;
  if (abs_second_a < abs_second_b)
    return -1;
  else if (abs_second_a > abs_second_b)
    return 1;
  return 0;
}

uint32 time_diff(struct DATE_TIME_T* old_time, struct DATE_TIME_T* new_time)
{
  uint16 year = old_time->year;
  uint8 month = old_time->month;
  uint8 date = old_time->date;
  uint16 delta_day = 0;
  uint32 start_time;
  uint32 end_time;
  const uint32 very_big_difference = 86400 * 10;
  // unreasonably different?
  if (time_compare(old_time, new_time) > 0)
    return very_big_difference;
  if (old_time->year != new_time->year)
    return very_big_difference;

  while (1)
  {
    if ((year >= new_time->year) &&
        (month >= new_time->month) &&
          (date >= new_time->date))
    {
      break;
    }
    time_next_day(&year, &month, &date);
    delta_day++;
  }
  start_time = (uint32)old_time->second +
    60 * (uint32)(old_time->minute + 60 * old_time->hour);
  end_time = (uint32)delta_day * 86400 +
    new_time->second + 60 * (uint32)(new_time->minute + 60 * new_time->hour);
  return end_time - start_time;
}
